package com.rockbb.common.sso.session;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;

import com.rockbb.common.utilities.internet.Crawler;
import com.rockbb.common.utilities.security.StringDigest;
import com.rockbb.common.utilities.text.JacksonMapper;

public class BasicSessionFactory
{
	private static Logger logger = Logger.getLogger(BasicSessionFactory.class);
	private static final BasicSessionFactory instance = new BasicSessionFactory();
	private SessionMap session_pool = null;
	private int factory_mode;

	// variable and methods for session cleaner
	private long clean_flag;
	public long getClean_flag() {return clean_flag;}
	public void setClean_flag(long clean_flag) {this.clean_flag = clean_flag;}
	public void cleanUp() {session_pool.cleanUp();}

	// Variables for client mode
	private String sync_address;
	private int sync_mode;

	private BasicSessionFactory()
	{
		switch (SessionConstants.SESSION_STORAGE)
		{
			case SessionConstants.SESSION_STORE_MEMCACHED:
				session_pool = new MemcachedSessionMap();
				break;
			case SessionConstants.SESSION_STORE_NATIVE:
				default:
				session_pool = new HashMapSessionMap();
				break;
		}
		session_pool.initialise();

		clean_flag = System.currentTimeMillis();
		factory_mode = SessionConstants.SESSION_MODE;

		// Initialise vars for client mode
		sync_address = SessionConstants.SESSION_SYNC_ADDR;
		sync_mode = SessionConstants.SESSION_SYNC_MODE;
	}

	public static BasicSessionFactory getInstance()
	{
		return instance;
	}

	public Collection<String> getSessionKeys()
	{
		return session_pool.getKeys();
	}

	/*
	 * Don't edit the sessions got by this method
	 */
	public List<Session> getSessions(Collection<String> keys)
	{
		return session_pool.getSessions(keys);
	}

	public int countAll()
	{
		return session_pool.size();
	}

	/*
	 * Delete session
	 */
	public void deleteSession(String sid)
	{
		session_pool.remove(sid);
	}

	public void updateSessionUser(String sid, long uuid, int autologin)
	{
		session_pool.updateSessionUser(sid, uuid, autologin);
	}

	public void resetSession(String sid)
	{
		session_pool.resetSession(sid);
	}

	public void resetToken(String sid)
	{
		session_pool.resetSessionToken(sid);
	}

	/**
	 * session 同步的核心方法. 在客户端模式下, 如果本地无session, 会调用sync至服务器查询
	 * 
	 * @param session_id
	 * @param browser
	 * @param ip
	 * @param allow_null	true: return null if the session doesn't exist, otherwise it will return an anonymous session
	 * @param create	true: add the session to the pool if it doesn't exist
	 * @param update_session_time	true:update the session time if the session exists and is not null
	 * @return
	 */
	public Session getSession(
			String session_id, 
			String browser, 
			String ip, 
			boolean allow_null,
			boolean create, 
			boolean update_session_time)
	{
		Session session = null;

		if (session_id != null && session_id.length() > 0)
		{
			session = session_pool.get(session_id);
			if (session != null)
			{
				if (!sessionValidate(session, browser, ip))
				{
					logger.debug(
							"Session unmatch: "
							+ session.getSession_id() + ", "
							+ session.getSession_ip() + ", "
							+ session.getSession_browser()
							);
					session_pool.remove(session_id);
					session = null;
				}
				else
				{
					if (update_session_time && 
							(factory_mode == SessionConstants.FACTORY_MODE_SERVER
							|| sync_mode == SessionConstants.FACTORY_SYNC_MODE_AUTH))
					{
						session_pool.renewSession(session_id, ip);
					}
				}
			}
		}

		if (session == null && !allow_null)
		{
			if (factory_mode == SessionConstants.FACTORY_MODE_SERVER)
			{
				session = session_pool.createSession(SessionConstants.USER_UID_ANONYMOUS, browser, ip, create);
			}
			else
			{
				session = sync(session_id, browser, ip, create);
			}
		}

		logger.debug("Sessions: " + session_pool.size());
		return session;
	}

	private Session sync(String sid, String browser, String ip, boolean create)
	{
		Crawler httpChannel = new Crawler();
		ArrayList<NameValuePair> nvps = new ArrayList<NameValuePair>();
		nvps.add(new BasicNameValuePair(SessionConstants.PARAM_MODE, String
				.valueOf(SessionConstants.MODE_SESSION)));
		nvps.add(new BasicNameValuePair(SessionConstants.PARAM_SID, sid));
		nvps.add(new BasicNameValuePair(SessionConstants.PARAM_IP, ip));
		nvps.add(new BasicNameValuePair(SessionConstants.PARAM_AGENT, browser));
		String echo = null;
		try
		{
			echo = httpChannel.post(sync_address, nvps);
		}
		catch (Exception e)
		{
			logger.info("Unknown Exception");
			logger.debug("Exception details:", e);
		}
		Session session = null;
		if (echo != null && echo.length() > 32)
		{
			ObjectMapper mapper = JacksonMapper.getInstance();
			try
			{
				echo = StringDigest.base64ToString(echo);
				session = mapper.readValue(echo, Session.class);
				logger.info("Session sync: "+session.getSession_id()+", "+session.getSession_uuid());
			}
			catch (Exception e)
			{
				logger.info("Unknown Exception");
				logger.debug("Exception details:", e);
				session = null;
			}
		}

		if (session == null)
		{
			session = session_pool.createSession(SessionConstants.USER_UID_ANONYMOUS, browser, ip, create);
		}
		else
		{
			session.setSession_browser(browser);
			session.setSession_ip(ip);
			session.setSession_time(System.currentTimeMillis());
			session.setSession_autologin(SessionConstants.SESSION_AUTOLOGIN_OFF);
			session_pool.put(session.getSession_id(), session);
		}

		return session;
	}

	private boolean sessionValidate(Session session, String browser, String ip)
	{
		boolean flag = session.getSession_browser().equals(browser)
				&& ((SessionConstants.AUTH_WITH_IP == 1)? 
						session.getSession_ip().equals(ip) : true)
				&& !session.is_expired();
		return flag;
	}
}
